import { IdGenerator } from '@ai-sdk/provider-utils';
import { ServerResponse } from 'node:http';
import {
  CallWarning,
  FinishReason,
  LanguageModelRequestMetadata,
  ProviderMetadata,
} from '../types';
import { Source } from '../types/language-model';
import { LanguageModelResponseMetadata } from '../types/language-model-response-metadata';
import { LanguageModelUsage } from '../types/usage';
import { InferUIMessageChunk } from '../ui-message-stream/ui-message-chunks';
import { UIMessageStreamOnFinishCallback } from '../ui-message-stream/ui-message-stream-on-finish-callback';
import { UIMessageStreamResponseInit } from '../ui-message-stream/ui-message-stream-response-init';
import { InferUIMessageMetadata, UIMessage } from '../ui/ui-messages';
import { AsyncIterableStream } from '../util/async-iterable-stream';
import { ErrorHandler } from '../util/error-handler';
import { ContentPart } from './content-part';
import { GeneratedFile } from './generated-file';
import { Output } from './output';
import { InferCompleteOutput, InferPartialOutput } from './output-utils';
import { ReasoningOutput } from './reasoning-output';
import { ResponseMessage } from './response-message';
import { StepResult } from './step-result';
import { ToolApprovalRequestOutput } from './tool-approval-request-output';
import { DynamicToolCall, StaticToolCall, TypedToolCall } from './tool-call';
import { TypedToolError } from './tool-error';
import { StaticToolOutputDenied } from './tool-output-denied';
import {
  DynamicToolResult,
  StaticToolResult,
  TypedToolResult,
} from './tool-result';
import { ToolSet } from './tool-set';

export type UIMessageStreamOptions<UI_MESSAGE extends UIMessage> = {
  /**
   * The original messages. If they are provided, persistence mode is assumed,
   * and a message ID is provided for the response message.
   */
  originalMessages?: UI_MESSAGE[];

  /**
   * Generate a message ID for the response message.
   *
   * If not provided, no message ID will be set for the response message (unless
   * the original messages are provided and the last message is an assistant message).
   */
  generateMessageId?: IdGenerator;

  onFinish?: UIMessageStreamOnFinishCallback<UI_MESSAGE>;

  /**
   * Extracts message metadata that will be send to the client.
   *
   * Called on `start` and `finish` events.
   */
  messageMetadata?: (options: {
    part: TextStreamPart<ToolSet>;
  }) => InferUIMessageMetadata<UI_MESSAGE> | undefined;

  /**
   * Send reasoning parts to the client.
   * Default to true.
   */
  sendReasoning?: boolean;

  /**
   * Send source parts to the client.
   * Default to false.
   */
  sendSources?: boolean;

  /**
   * Send the finish event to the client.
   * Set to false if you are using additional streamText calls
   * that send additional data.
   * Default to true.
   */
  sendFinish?: boolean;

  /**
   * Send the message start event to the client.
   * Set to false if you are using additional streamText calls
   * and the message start event has already been sent.
   * Default to true.
   */
  sendStart?: boolean;

  /**
   * Process an error, e.g. to log it. Default to `() => 'An error occurred.'`.
   *
   * @return error message to include in the data stream.
   */
  onError?: (error: unknown) => string;
};

export type ConsumeStreamOptions = {
  onError?: ErrorHandler;
};

/**
A result object for accessing different stream types and additional information.
 */
export interface StreamTextResult<
  TOOLS extends ToolSet,
  OUTPUT extends Output,
> {
  /**
The content that was generated in the last step.

Automatically consumes the stream.
   */
  readonly content: Promise<Array<ContentPart<TOOLS>>>;

  /**
The full text that has been generated by the last step.

Automatically consumes the stream.
     */
  readonly text: Promise<string>;

  /**
The full reasoning that the model has generated.

Automatically consumes the stream.
   */
  readonly reasoning: Promise<Array<ReasoningOutput>>;

  /**
The reasoning that has been generated by the last step.

Automatically consumes the stream.
     */
  readonly reasoningText: Promise<string | undefined>;

  /**
Files that have been generated by the model in the last step.

Automatically consumes the stream.
   */
  readonly files: Promise<GeneratedFile[]>;

  /**
Sources that have been used as references in the last step.

Automatically consumes the stream.
   */
  readonly sources: Promise<Source[]>;

  /**
The tool calls that have been executed in the last step.

Automatically consumes the stream.
     */
  readonly toolCalls: Promise<TypedToolCall<TOOLS>[]>;

  /**
The static tool calls that have been executed in the last step.

Automatically consumes the stream.
     */
  readonly staticToolCalls: Promise<StaticToolCall<TOOLS>[]>;

  /**
The dynamic tool calls that have been executed in the last step.

Automatically consumes the stream.
     */
  readonly dynamicToolCalls: Promise<DynamicToolCall[]>;

  /**
The static tool results that have been generated in the last step.

Automatically consumes the stream.
     */
  readonly staticToolResults: Promise<StaticToolResult<TOOLS>[]>;

  /**
The dynamic tool results that have been generated in the last step.

Automatically consumes the stream.
     */
  readonly dynamicToolResults: Promise<DynamicToolResult[]>;

  /**
The tool results that have been generated in the last step.

Automatically consumes the stream.
   */
  readonly toolResults: Promise<TypedToolResult<TOOLS>[]>;

  /**
The reason why the generation finished. Taken from the last step.

Automatically consumes the stream.
     */
  readonly finishReason: Promise<FinishReason>;

  /**
The token usage of the last step.

Automatically consumes the stream.
   */
  readonly usage: Promise<LanguageModelUsage>;

  /**
The total token usage of the generated response.
When there are multiple steps, the usage is the sum of all step usages.

Automatically consumes the stream.
     */
  readonly totalUsage: Promise<LanguageModelUsage>;

  /**
Warnings from the model provider (e.g. unsupported settings) for the first step.

Automatically consumes the stream.
     */
  readonly warnings: Promise<CallWarning[] | undefined>;

  /**
Details for all steps.
You can use this to get information about intermediate steps,
such as the tool calls or the response headers.

Automatically consumes the stream.
   */
  readonly steps: Promise<Array<StepResult<TOOLS>>>;

  /**
Additional request information from the last step.

Automatically consumes the stream.
 */
  readonly request: Promise<LanguageModelRequestMetadata>;

  /**
Additional response information from the last step.

Automatically consumes the stream.
 */
  readonly response: Promise<
    LanguageModelResponseMetadata & {
      /**
The response messages that were generated during the call. It consists of an assistant message,
potentially containing tool calls.

When there are tool results, there is an additional tool message with the tool results that are available.
If there are tools that do not have execute functions, they are not included in the tool results and
need to be added separately.
       */
      messages: Array<ResponseMessage>;
    }
  >;

  /**
Additional provider-specific metadata from the last step.
Metadata is passed through from the provider to the AI SDK and
enables provider-specific results that can be fully encapsulated in the provider.
   */
  readonly providerMetadata: Promise<ProviderMetadata | undefined>;

  /**
  A text stream that returns only the generated text deltas. You can use it
  as either an AsyncIterable or a ReadableStream. When an error occurs, the
  stream will throw the error.
     */
  readonly textStream: AsyncIterableStream<string>;

  /**
  A stream with all events, including text deltas, tool calls, tool results, and
  errors.
  You can use it as either an AsyncIterable or a ReadableStream.
  Only errors that stop the stream, such as network errors, are thrown.
     */
  readonly fullStream: AsyncIterableStream<TextStreamPart<TOOLS>>;

  /**
   * A stream of partial outputs. It uses the `output` specification.
   *
   * @deprecated Use `partialOutputStream` instead.
   */
  readonly experimental_partialOutputStream: AsyncIterableStream<
    InferPartialOutput<OUTPUT>
  >;

  /**
   * A stream of partial parsed outputs. It uses the `output` specification.
   */
  readonly partialOutputStream: AsyncIterableStream<InferPartialOutput<OUTPUT>>;

  /**
   * The complete parsed output. It uses the `output` specification.
   */
  readonly output: Promise<InferCompleteOutput<OUTPUT>>;

  /**
Consumes the stream without processing the parts.
This is useful to force the stream to finish.
It effectively removes the backpressure and allows the stream to finish,
triggering the `onFinish` callback and the promise resolution.

If an error occurs, it is passed to the optional `onError` callback.
  */
  consumeStream(options?: ConsumeStreamOptions): Promise<void>;

  /**
Converts the result to a UI message stream.

@return A UI message stream.
     */
  toUIMessageStream<UI_MESSAGE extends UIMessage>(
    options?: UIMessageStreamOptions<UI_MESSAGE>,
  ): AsyncIterableStream<InferUIMessageChunk<UI_MESSAGE>>;

  /**
   *Writes UI message stream output to a Node.js response-like object.
   */
  pipeUIMessageStreamToResponse<UI_MESSAGE extends UIMessage>(
    response: ServerResponse,
    options?: UIMessageStreamResponseInit & UIMessageStreamOptions<UI_MESSAGE>,
  ): void;

  /**
Writes text delta output to a Node.js response-like object.
It sets a `Content-Type` header to `text/plain; charset=utf-8` and
writes each text delta as a separate chunk.

@param response A Node.js response-like object (ServerResponse).
@param init Optional headers, status code, and status text.
     */
  pipeTextStreamToResponse(response: ServerResponse, init?: ResponseInit): void;

  /**
Converts the result to a streamed response object with a stream data part stream.

@return A response object.
     */
  toUIMessageStreamResponse<UI_MESSAGE extends UIMessage>(
    options?: UIMessageStreamResponseInit & UIMessageStreamOptions<UI_MESSAGE>,
  ): Response;

  /**
  Creates a simple text stream response.
  Each text delta is encoded as UTF-8 and sent as a separate chunk.
  Non-text-delta events are ignored.
  @param init Optional headers, status code, and status text.
     */
  toTextStreamResponse(init?: ResponseInit): Response;
}

export type TextStreamPart<TOOLS extends ToolSet> =
  | {
      type: 'text-start';
      id: string;
      providerMetadata?: ProviderMetadata;
    }
  | {
      type: 'text-end';
      id: string;
      providerMetadata?: ProviderMetadata;
    }
  | {
      type: 'text-delta';
      id: string;
      providerMetadata?: ProviderMetadata;
      text: string;
    }
  | {
      type: 'reasoning-start';
      id: string;
      providerMetadata?: ProviderMetadata;
    }
  | {
      type: 'reasoning-end';
      id: string;
      providerMetadata?: ProviderMetadata;
    }
  | {
      type: 'reasoning-delta';
      providerMetadata?: ProviderMetadata;
      id: string;
      text: string;
    }
  | {
      type: 'tool-input-start';
      id: string;
      toolName: string;
      providerMetadata?: ProviderMetadata;
      providerExecuted?: boolean;
      dynamic?: boolean;
      title?: string;
    }
  | {
      type: 'tool-input-end';
      id: string;
      providerMetadata?: ProviderMetadata;
    }
  | {
      type: 'tool-input-delta';
      id: string;
      delta: string;
      providerMetadata?: ProviderMetadata;
    }
  | ({ type: 'source' } & Source)
  | { type: 'file'; file: GeneratedFile } // different because of GeneratedFile object
  | ({ type: 'tool-call' } & TypedToolCall<TOOLS>)
  | ({ type: 'tool-result' } & TypedToolResult<TOOLS>)
  | ({ type: 'tool-error' } & TypedToolError<TOOLS>)
  | ({ type: 'tool-output-denied' } & StaticToolOutputDenied<TOOLS>)
  | ToolApprovalRequestOutput<TOOLS>
  | {
      type: 'start-step';
      request: LanguageModelRequestMetadata;
      warnings: CallWarning[];
    }
  | {
      type: 'finish-step';
      response: LanguageModelResponseMetadata;
      usage: LanguageModelUsage;
      finishReason: FinishReason;
      providerMetadata: ProviderMetadata | undefined;
    }
  | {
      type: 'start';
    }
  | {
      type: 'finish';
      finishReason: FinishReason;
      totalUsage: LanguageModelUsage;
    }
  | {
      type: 'abort';
    }
  | {
      type: 'error';
      error: unknown;
    }
  | {
      type: 'raw';
      rawValue: unknown;
    };
